<?php

class DBMEntityManager {
	private $connection;
	
	public function __construct($connection){
		// check the type of the connection and store
		if($connection instanceof DBMConnection){
			$this->connection = $connection;
		} else {
			throw new IllegalArgumentException("DBMConnection", gettype($connection));
		}
	}
	
	public function getConnection(){
		return $this->connection;
	}
	
	public function setConnection(DBMConnection $connection){
		$this->connection = $connection;
	}
	
	public function getEntityHolder(){
		return $this->entityHolder;
	}
	
	public function setEntityHolder(DBMEntityHolder $holder){
		$this->entityHolder = $holder;
	}
	
	public function getEntity($entity){
		if(is_object($entity) && $entity instanceof DBMEntity){
			return $entity;
		} else {
			return DBMEntityHolder::getInstance()->getEntity($entity);	
		}
	}

	public function createCommand($entityName, $alias = null){
		// get the entity
		$entity = $this->getEntity($entityName);
		
		// check the entity integrity
		if($entity === false || is_null($entity)){
			throw new IllegalEntityException($entityName);
		}
		
		// set the alias, if needed
		if(!is_null($alias)){
			$entity->setAlias($alias);
		}
				
		// create an empty command
		$command = new DBMCommand();
		$command->addEntity($entity);
		
		// return 
		return $command;
	}

	public function createSelectCommand($entityName, $alias = null){
		// get the entity
		$entity = $this->getEntity($entityName);
		
		// set the alias, if needed
		if(!is_null($alias)){
			$entity->setAlias($alias);
		}
				
		// create an empty command
		$command = new DBMSelectCommand();
		$command->addEntity($entity);
		
		// return 
		return $command;
	}
	
	public function loadRelationships($obj){
		// get the class data
		$cls = get_class($obj);
		
		// get the entity specification
		$entity = DBMEntityHolder::getInstance()->getEntity($cls);
		
		// check if the entity is valid
		if(is_null($entity)){
			throw new IllegalEntityException($cls);
		}
		
		// get the relationships
		$relationships = $entity->getRelationships();
		
		// search the relationships
		foreach($relationships as $relationship){
			// get the attribute, the target entity and the connections
			$attribute		= $relationship->getAttribute();
			$connections	= $relationship->getConnections();
			
			$targetEntity 	= DBMEntityHolder::getInstance()->getEntity($relationship->getTargetEntity());
			
			// find the setter for the attribute
			$setterMethod = DBMReflectionHelper::findSetter($cls, $attribute);
			$getterMethod = DBMReflectionHelper::findGetter($cls, $attribute);
			
			// check the data
			if(is_object($obj->$getterMethod()) && get_class($obj->$getterMethod()) == $targetEntity->getClass()){
				$this->loadRelationships($obj->$getterMethod());
				continue;
			}
			
			// create the needed variables
			$columns = array();
					
			// check the integrity of the relationship
			if(is_null($attribute) || is_null($targetEntity) || !is_array($connections) || count($connections) <= 0){
				throw new IllegalRelationshipException("The relationship for the attribute " . $entity->getName() . "::" . $attribute . " contains errors, please check your configuration!");
			}

			// search the connections
			foreach($connections as $connection){
				// get the source and target attributes
				$target = $targetEntity->getAttributeByName($connection->getTargetAttribute());
				
				// find the getter
				$getterMethod = DBMReflectionHelper::findGetter($cls, $connection->getSourceAttribute());
				
				// get the value of the source attribute
				$sourceValue = $obj->$getterMethod();

				// set the value on the filters
				if(is_null($sourceValue)){
					$columns[$target->getColumn()] = "NULL";	
				} else {
					$columns[$target->getColumn()] = $sourceValue;	
				}
			}
			
			try{
				// find the results by the columns
				$results = $this->findByColumns($targetEntity, $columns);	
			} catch(EmptyResultException $e){
				// if the relationship has not been found, set as null and continue
				$obj->$setterMethod(NULL);
				continue;
			}

			// set the data into the object
			if($relationship->getType() == DBMRelationship::TYPE_ONE_TO_ONE){
				$obj->$setterMethod(array_shift($results));	
			} elseif($relationship->getType() == DBMRelationship::TYPE_ONE_TO_MANY){
				$obj->$setterMethod($results);
			}
		}
		
		// return the object
		return $obj;
	}

	public function runQuery($sql, $createResultset = false){
		// run the query
		return $this->getConnection()->runQuery($sql, $createResultset);
	}

	

	public function find($entity, $primaryKeyValue){
		// get the entity
		$entity = $this->getEntity($entity);
		
		// get the primary key
		$primaryKey = $entity->getPrimaryKey();
		
		// check the primary key integrity
		if(is_null($primaryKey)){
			throw new IllegalEntityException($entity);
			
		}
		
		// get the parts of the primary key
		$pkAttributes = $primaryKey->getAttributes();

		// create the needed variables
		$filters = array();
		
		// add the filters
		if(count($pkAttributes) == 1){
			$pkAttribute 	= array_shift($pkAttributes);
			$pkColumn		= $pkAttribute->getColumn();
			$filters[] 		= new DBMLogicalFilter($pkColumn, DBMLogicalFilter::EQUALS, $primaryKeyValue);
		} else {
			// search the primary key attributes
			foreach($pkAttributes as $pkAttribute){
				// get the column
				$pkColumn = $pkAttribute->getColumn();
				
				// check if the value is been seted
				if(isset($primaryKeyValue[$pkColumn])){
					$filters[] = new DBMLogicalFilter($pkColumn, DBMLogicalFilter::EQUALS, $primaryKeyValue[$pkColumn]);	
				} else {
					throw new IllegalArgumentException("array with ALL of the primary key columns", "unknow");
				}
			}
		}
		
		// find
		return @array_shift($this->findByFilters($entity, $filters));
	}
	
	public function findByFilters($entity, $filters = array()){
		// create a new command for this entity
		$command = $this->createCommand($entity);
		
		// search the filters
		foreach($filters as $filter){
			$command->addFilter($filter);	
		}
		
		// set the command operation
		$command->setOperation(DBMOperations::SELECT);

		// find
		return $this->findByCommand($command);
	}
	
	public function findByColumn($entity, $columnName, $columnValue){
		// create the filters
		$filters = array(
			new DBMLogicalFilter($columnName, DBMLogicalFilter::EQUALS, $columnValue)
		);
		
		// find
		return $this->findByFilters($entity, $filters);
	}
	
	public function findByColumns($entity, $columns){
		// create the needed variables
		$filters = array();
		
		// search the columns
		foreach($columns as $columnName => $columnValue){
			// create the filter
			$filters[] = new DBMLogicalFilter($columnName, DBMLogicalFilter::EQUALS, $columnValue);			
		}
		
		// find
		return $this->findByFilters($entity, $filters);
	}
	
	public function findByCommand(DBMCommand $command){
		// get the main entity and the class
		$entity = $command->getMainEntity();
		$class = $entity->getClass();
		
		// set the operation of the command
		$command->setOperation(DBMOperations::SELECT);
		
		// create the collection
		$collection = array();
		
		// run
		$resultset = $this->connection->executeSelect($command);
		
		// convert to objects
		$objects = DBMConversor::resultsetToObject($resultset, $entity);
		
		// check if this command is lazy-load enabled
		if(!($command instanceof DBMSelectCommand) || ($command instanceof DBMSelectCommand && $command->isLazyLoad())){
			// search the objects
			foreach($objects as $object){
				// lazy-load
				$this->loadRelationships($object);
			}
		}
		
		// return
		return $objects;
	}
	
	public function getAll(DBMCommand $command){
		return $this->findByCommand($command);
	}
	
	public function insert($object){
		// get the entity
		$entity = DBMConversor::objectToEntity($object);
		
		// create a new command 
		$command = new DBMCommand();
		$command->addEntity($entity);
		$command->setOperation(DBMOperations::INSERT);
		
		// run the command
		$result = $this->connection->executeInsert($command);
		
		// return 
		return $result;
	}

	public function update($object){
		throw new Exception("Not implemented!");
		
		// get the entity
		$entity = DBMConversor::objectToEntity($object);
		
		// create a new command 
		$command = new DBMCommand();
		$command->addEntity($entity);
		$command->setOperation(DBMOperations::UPDATE);
		
		// get the entity's primary key
		$pk = $entity->getPrimaryKey();
		
		// get the attributes of the primary key
		$pkAttributes = $pk->getAttributes();
		
		// setup the filters
		foreach($pkAttributes as $attribute){
			$command->addFilter(new DBMLogicalFilter($attribute->getColumn(), DBMLogicalFilter::EQUALS, $attribute->getValue()));
		}
		
		// run the command
		return $this->connection->executeUpdate($command);
	}

	public function updateEntity($object, $filters = array()){
		// get the entity
		$entity = DBMConversor::objectToEntity($object);
		
		// create a new command 
		$command = new DBMCommand();
		$command->addEntity($entity);
		$command->setOperation(DBMOperations::UPDATE);
		
		// add the filters
		foreach($filters as $filter){
			// check the integrity of the filter
			$command->addFilter($filter);
		}
		
		// run the command
		return $this->connection->executeUpdate($command);
	}
	
	public function delete($object){
		throw new Exception("Not implemented!");
	}
	
	public function deleteEntity($objectOrEntity, $filters){
		// get the entity
		if(is_string($objectOrEntity)){
			$entity = $this->getEntity($objectOrEntity);
		} else {
			$entity = DBMConversor::objectToEntity($objectOrEntity);
		}
		
		// create a new command 
		$command = new DBMCommand();
		$command->addEntity($entity);
		$command->setOperation(DBMOperations::DELETE);
		
		// add the filters
		foreach($filters as $filter){
			// check the integrity of the filter
			$command->addFilter($filter);
		}
		
		// run the command
		return $this->connection->executeDelete($command);
	}
}
?>